/*
 * request_generator_form.c
 * This file is part of gURL
 *
 * Copyright (C) 2013, 2014 - Aleksey Konovalov
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Library General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "request_generator_form.h"

#include <assert.h>
#include "defines.h"
#include <glib/gi18n.h>
#include <gurl_conf.h>
#include "requests_list.h"
#include "session.h"
#include <stdbool.h>
#include <stdlib.h>
#include "text_edit.h"
#include "utils.h"

typedef struct {
    method_type type;
    const char* name;
    bool has_body;
} http_method_info;

static http_method_info methods_info[] = {
    { mGET,    "GET",    false },
    { mPOST,   "POST",   true  },
    { mHEAD,   "HEAD",   false },
    { mDELETE, "DELETE", false },
    { mPUT,    "PUT",    true  },
};

#define ARRAY_SIZE(a) (sizeof((a)) / sizeof(*(a)))

/* Signals */
enum {
    NEW_SESSION,
    LAST_SIGNAL
};

struct _RequestGeneratorForm {
    GtkGrid grid;

    /* private */
    GtkWidget* url_input;
    GtkWidget* url_combo;
    GtkComboBox* method_combo;
    GtkWidget* req_headers_tview;
    GtkWidget* req_body_tedit;
};

struct _RequestGeneratorFormClass {
    GtkGridClass parent;

    guint signals[LAST_SIGNAL];
};

static const char* const CONF_GROUP = "last request";
static const char* const CONF_METHOD_KEY = "method";
static const char* const CONF_URL_KEY = "URL";
static const char* const CONF_URL_HISTORY_KEY = "URL history";
static const char* const CONF_URL_HISTORY_MAX_LEN = "URL history max length";
static const char* const CONF_HEADERS_KEY = "headers";
static const char* const CONF_BODY_KEY = "body";
static const char* const CONF_BODY_ENC_KEY = "body encoding";


static void on_make_request_btn_clicked(GtkWidget* widget,
                                        RequestGeneratorForm* form);
static void on_method_combo_changed(GtkWidget* widget,
                                    RequestGeneratorForm* form);

G_DEFINE_TYPE(RequestGeneratorForm, request_generator_form, GTK_TYPE_GRID)

static void request_generator_form_class_init(RequestGeneratorFormClass* klass) {
    klass->signals[NEW_SESSION] =
        g_signal_new("new-session", REQUEST_GENERATOR_FORM_TYPE,
                     G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE,
                     1, G_TYPE_POINTER);
}

static void request_generator_form_init(RequestGeneratorForm* self) {
    gtk_grid_set_row_spacing(GTK_GRID(self), 5);
    gtk_grid_set_column_spacing(GTK_GRID(self), 5);
    gtk_widget_set_margin_left(GTK_WIDGET(self), 5);
    gtk_widget_set_margin_top(GTK_WIDGET(self), 5);
    gtk_widget_set_margin_right(GTK_WIDGET(self), 5);
    gtk_widget_set_margin_bottom(GTK_WIDGET(self), 5);

    self->method_combo = GTK_COMBO_BOX(gtk_combo_box_text_new());
    gtk_widget_show(GTK_WIDGET(self->method_combo));
    gtk_grid_attach(GTK_GRID(self), GTK_WIDGET(self->method_combo), 0, 0, 1, 1);
    for(size_t i = 0; i < ARRAY_SIZE(methods_info); ++i)
        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(self->method_combo),
                                  NULL, methods_info[i].name);
    gtk_combo_box_set_active(self->method_combo, 0);
    g_signal_connect(G_OBJECT(self->method_combo), "changed",
                     G_CALLBACK(on_method_combo_changed), self);

    self->url_combo = gtk_combo_box_text_new_with_entry();
    gtk_widget_show(GTK_WIDGET(self->url_combo));
    gtk_widget_set_hexpand(self->url_combo, TRUE);
    gtk_grid_attach(GTK_GRID(self), self->url_combo, 1, 0, 1, 1);
    self->url_input = gtk_bin_get_child(GTK_BIN(self->url_combo));

    GtkWidget* make_request_btn = gtk_button_new_with_label(_("execute"));
    gtk_widget_show(make_request_btn);
    gtk_grid_attach(GTK_GRID(self), make_request_btn, 2, 0, 1, 1);
    g_signal_connect(G_OBJECT(make_request_btn), "clicked",
                     G_CALLBACK(on_make_request_btn_clicked), self);

    GtkWidget* req_headers_label = gtk_label_new(_("headers"));
    gtk_widget_show(req_headers_label);
    gtk_misc_set_alignment(GTK_MISC(req_headers_label), 0.0, 0.5);
    gtk_grid_attach(GTK_GRID(self), req_headers_label, 0, 1, 3, 1);
    self->req_headers_tview = gtk_text_view_new();
    gtk_widget_show(self->req_headers_tview);
    gtk_widget_set_hexpand(self->req_headers_tview, true);
    gtk_widget_set_vexpand(self->req_headers_tview, true);
    gtk_grid_attach(GTK_GRID(self), add_to_scrolled_window(self->req_headers_tview),
                    0, 2, 3, 1);

    GtkWidget* req_body_label = gtk_label_new(_("body"));
    gtk_widget_show(req_body_label);
    gtk_misc_set_alignment(GTK_MISC(req_body_label), 0.0, 0.5);
    gtk_grid_attach(GTK_GRID(self), req_body_label, 0, 3, 3, 1);

    self->req_body_tedit = text_edit_new();
    gtk_widget_show(self->req_body_tedit);
    gtk_grid_attach(GTK_GRID(self), self->req_body_tedit, 0, 4, 3, 1);

    on_method_combo_changed(GTK_WIDGET(self->method_combo), self);
}

static void on_method_combo_changed(GtkWidget* UNUSED(widget),
                                    RequestGeneratorForm* form) {
    int cur_method = gtk_combo_box_get_active(form->method_combo);
    gtk_widget_set_sensitive(form->req_body_tedit,
                             methods_info[cur_method].has_body);
}

static char* get_text_from_tbuffer(GtkTextBuffer* buffer) {
    GtkTextIter start, end;
    gtk_text_buffer_get_start_iter(buffer, &start);
    gtk_text_buffer_get_end_iter(buffer, &end);
    return gtk_text_buffer_get_text(buffer, &start, &end, false);
}

static void store_url_to_history(const char* url, RequestGeneratorForm* form) {
    GtkTreeModel* model = gtk_combo_box_get_model(
        GTK_COMBO_BOX(form->url_combo));
    GtkTreeIter iter = {0};
    if(!gtk_tree_model_get_iter_first(model, &iter))
        return;
    const int max_len = config_get_int(CONF_GROUP, CONF_URL_HISTORY_MAX_LEN, 10);
    for(int len = 0;;) {
        gchar* item_text = NULL;
        gtk_tree_model_get(model, &iter, 0, &item_text, -1);
        gboolean need_remove = item_text && strcmp(item_text, url) == 0;
        g_free(item_text);
        if(!need_remove)
            need_remove = ++len >= max_len;
        if(!(need_remove ? gtk_list_store_remove(GTK_LIST_STORE(model), &iter) :
                           gtk_tree_model_iter_next(model, &iter) ))
            break;
    }
    gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(form->url_combo), url);
}

static void on_make_request_btn_clicked(GtkWidget* UNUSED(widget),
                                        RequestGeneratorForm* form) {
    int cur_method = gtk_combo_box_get_active(form->method_combo);
    const char* url = gtk_entry_get_text(GTK_ENTRY(form->url_input));
    store_url_to_history(url, form);
    char* headers = get_text_from_tbuffer(gtk_text_view_get_buffer(
        GTK_TEXT_VIEW(form->req_headers_tview)));
    char* encoded_body = NULL;
    gsize encoded_body_len = 0;
    Encoding encoding = text_edit_get_encoding(TEXT_EDIT(form->req_body_tedit));
    if(methods_info[cur_method].has_body) {
        char* body = get_text_from_tbuffer(text_edit_get_buffer(
            TEXT_EDIT(form->req_body_tedit)));
        encoded_body = g_convert_with_fallback(body, -1,
            get_encoding_name(encoding), "UTF8", "?", NULL,
            &encoded_body_len, NULL);
        /* FIXME сделать адекватную обработку ошибок */
        if(!encoded_body) {
            encoded_body = "";
            encoded_body_len = 0;
        }
        g_free(body);
    }
    Session* request = session_new(methods_info[cur_method].type, url,
                                   headers, encoded_body, encoded_body_len, encoding);
    g_signal_emit(form, REQUEST_GENERATOR_FORM_GET_CLASS(form)->signals[NEW_SESSION], 0, request);
    g_object_unref(request);
    if(encoded_body)
        g_free(encoded_body);
    g_free(headers);
}

void request_generator_form_load_config(RequestGeneratorForm* self) {
    gchar* method_name = config_get_str(CONF_GROUP, CONF_METHOD_KEY, "");
    int method_ind = 0;
    for(size_t i = 0; i < ARRAY_SIZE(methods_info); ++i)
        if(!strcmp(method_name, methods_info[i].name)) {
            method_ind = i;
            break;
        }
    g_free(method_name);
    gtk_combo_box_set_active(self->method_combo, method_ind);

    gchar* url = config_get_str(CONF_GROUP, CONF_URL_KEY, "");
    gtk_entry_set_text(GTK_ENTRY(self->url_input), url);
    g_free(url);

    gchar** url_history = config_get_str_list(
        CONF_GROUP, CONF_URL_HISTORY_KEY, NULL);
    if(url_history) {
        for(gchar** i = url_history; *i; ++i)
            gtk_combo_box_text_append_text(
                GTK_COMBO_BOX_TEXT(self->url_combo), *i);
        g_strfreev(url_history);
    }

    gchar* headers = config_get_str(CONF_GROUP, CONF_HEADERS_KEY, "");
    gtk_text_buffer_set_text(
        gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->req_headers_tview)),
        headers, -1);
    g_free(headers);

    gchar* body = config_get_str(CONF_GROUP, CONF_BODY_KEY, "");
    gtk_text_buffer_set_text(
        text_edit_get_buffer(TEXT_EDIT(self->req_body_tedit)), body, -1);
    g_free(body);

    gchar* encoding = config_get_str(CONF_GROUP, CONF_BODY_ENC_KEY, "ASCII");
    Encoding enc = get_encoding_by_name(encoding);
    if(enc != UNKNOWN_ENCODING)
        text_edit_set_encoding(TEXT_EDIT(self->req_body_tedit), enc);
    g_free(encoding);
}

void request_generator_form_save_config(RequestGeneratorForm* self) {
    int method = gtk_combo_box_get_active(self->method_combo);
    config_set_str(CONF_GROUP, CONF_METHOD_KEY, methods_info[method].name);
    config_set_str(CONF_GROUP, CONF_URL_KEY,
                   gtk_entry_get_text(GTK_ENTRY(self->url_input)));

    GtkTreeModel* model = gtk_combo_box_get_model(
        GTK_COMBO_BOX(self->url_combo));
    int urls_cnt = gtk_tree_model_iter_n_children(model, NULL);
    if(urls_cnt > 0) {
        gchar** urls = g_malloc(sizeof(gchar*) * (urls_cnt + 1));
        urls[urls_cnt] = NULL;
        GtkTreeIter iter;
        if(gtk_tree_model_get_iter_first(model, &iter)) {
            for(int i = 0; i < urls_cnt; ++i) {
                gtk_tree_model_get(model, &iter, 0, &urls[i], -1);
                gtk_tree_model_iter_next(model, &iter);
            }
            config_set_str_list(CONF_GROUP, CONF_URL_HISTORY_KEY,
                                (const gchar**)urls, urls_cnt);
        }
        g_strfreev(urls);
    }

    gchar* headers = get_text_from_tbuffer(gtk_text_view_get_buffer(
        GTK_TEXT_VIEW(self->req_headers_tview)));
    config_set_str(CONF_GROUP, CONF_HEADERS_KEY, headers);
    g_free(headers);

    gchar* body = get_text_from_tbuffer(text_edit_get_buffer(
        TEXT_EDIT(self->req_body_tedit)));
    config_set_str(CONF_GROUP, CONF_BODY_KEY, body);
    g_free(body);

    config_set_str(CONF_GROUP, CONF_BODY_ENC_KEY, get_encoding_name(
        text_edit_get_encoding(TEXT_EDIT(self->req_body_tedit))));
}

GtkWidget* request_generator_form_new() {
    return g_object_new(REQUEST_GENERATOR_FORM_TYPE, NULL);
}
